<template>
  <div class="svg-map" ref="root" style="height: 100%;width: 100%">
    <ArkChart
      v-if="!destroyInstance"
      :init-theme="initTheme"
      :init-opts="initOpts"
      :options="optionsVariable"
      @instanced="instancedAfter"/>
  </div>
</template>

<script>
import {defineComponent} from 'vue';
import _ from 'lodash';
import * as echarts from 'echarts';
import {ElLoading} from 'element-plus';
import ArkChart from "@/components/base-component-v1/ark-chart/ArkChart.vue";

export default defineComponent({
  name: 'ArkSvgMap',
  components: {ArkChart},
  emit: ['instanced', 'clickBlank', 'mouseoverBlock', 'clickBlock', 'mouseoverCircle', 'clickCircle', 'zoomMap'],
  props: {
    /**
     * SvgMap文件结构<svg><g>...</g></svg>，以下为...中标签元素的注意
     *  - <title>标签元素将被移除
     *  - <image>标签元素将被忽略
     *  - <circle>和<ellipse>表示点位，用散点图表述位置，其他元素为块
     */
    svgPathOrReq: {
      type: [String, Function],
      required: true,
    },
    mapName: {
      type: String,
      default: 'MAP'
    },
    initTheme: {
      type: [String, Object],
    },
    initOpts: {
      type: Object,
    },
    keepDefaultOptions: {
      type: Boolean,
      default: true,
    },
    /**
     * options注意点
     *  - 散点series[1]的data前面的数据将与SVG中的<circle>和<ellipse>数据合并，超出的部分默认坐标为[50,50]
     *  - 默认series[1]的data的length有多长就有多少个散点，超出的部分默认坐标为[50,50]
     *  - 散点series[1]的markPoint.data与series[1]的data相对应，且数据是合并形成的
     */
    options: {
      type: Object,
    },
    handleSvgDomAfter: {
      type: Function,
      default: () => { }
    },
  },
  data() {
    return {
      // 利用v-if注册和销毁echarts实例，便于地图切换
      destroyInstance: true,
      loadingInstance: {},
      allElsInfo: {},
      elsDefaultInfo: {
        id: '???',
        name: '名称',
        node: null,
        circle: true,
        value: [50, 50],
        itemStyle: {color: '#FF3B30FF'}
      }
    };
  },
  computed: {
    reloadSvgMap() {
      return [this.svgPathOrReq, this.mapName, this.keepDefaultOptions];
    },
    optionsVariable() {
      let options = _.cloneDeep(this.options);
      const mapName = this.mapName;
      let allElsInfo = _.cloneDeep(this.allElsInfo);
      let elsDefaultInfo = _.cloneDeep(this.elsDefaultInfo);
      const keepDefaultOptions = this.keepDefaultOptions;

      // === 不与默认配置合并 ===
      if (!keepDefaultOptions) {
        if (!options) options = {};

        delete options?.geo?.map;
        return _.defaultsDeep(options, {geo: {map: mapName}});
      }

      // === 与默认配置合并 ===
      // 获取散点数据
      let circleEls = allElsInfo.filter(item => item.circle);
      let needAllElsInfo = _.cloneDeep(circleEls);
      if (options?.series[1]?.data && options?.series[1]?.data.length >= 1) {
        needAllElsInfo = [];
        options.series[1].data.forEach((item, index) => {
          let defaultItem = _.cloneDeep(elsDefaultInfo);
          if (index < circleEls.length) {
            defaultItem = circleEls[index];
          }
          needAllElsInfo.push(_.defaultsDeep(item, defaultItem));
        });
      }
      const circles = needAllElsInfo.filter(item => item.circle);
      // 获取标注数据
      const marks = needAllElsInfo.map((item, index) => {
        if (index > needAllElsInfo.length) return;

        let provideItem = {};
        if (options?.series[1]?.markPoint?.data && index < options?.series[1]?.markPoint?.data.length) {
          provideItem = options.series[1].markPoint.data[index];
        }
        return _.defaultsDeep(provideItem, {...item, coord: item.value});
      });
      // 默认配置
      const defaultOptions = {
        geo: {
          // 注册的地图名称
          map: mapName,
          // 开启鼠标缩放和平移漫游
          roam: true,
          // 当前视角的缩放比例
          zoom: 1,
          // 滚轮缩放的极限控制
          scaleLimit: {
            min: 0.4,
            max: 10,
          },
          // 图形上的文本标签
          label: {show: false},
          // 地图区域的多边形图形样式
          itemStyle: {
            color: 'transparent',
            borderColor: '#00F2FC4C',
            shadowColor: '#00F2FC',
            borderWidth: 1,
            shadowBlur: 7,
          },
          // 高亮状态下的多边形和标签样式
          emphasis: {
            label: {show: false},
            itemStyle: {
              color: '#4F7FFF',
              borderColor: '#00F2FC99',
              shadowColor: '#00F2FC',
              borderWidth: 2,
              shadowBlur: 10,
            },
          },
          // 选中状态下的多边形和标签样式
          select: {
            label: {show: false},
            itemStyle: {
              color: '#4F7FFF',
              borderColor: '#00F2FC99',
              shadowColor: '#00F2FC',
              borderWidth: 2,
              shadowBlur: 10,
            },
          },
        },
        tooltip: {trigger: 'item'},
        series: [
          {
            type: 'map',
            geoIndex: 0,
            tooltip: {
              show: true,
              enterable: true,
              extraCssText: 'border: none;border-radius: 15px',
              formatter: (params) => {
                return params.name;
              },
            },
          },
          {
            type: 'effectScatter',
            coordinateSystem: 'geo',
            tooltip: {show: false},
            data: needAllElsInfo,
            markPoint: {
              tooltip: {show: false},
              label: {
                show: true,
                position: 'top',
                formatter(params) {
                  return params.name;
                },
              },
              data: marks,
            },
          },
        ]
      };

      delete options?.geo?.map;
      delete options?.series[1]?.markPoint?.data;
      return _.defaultsDeep(options, defaultOptions);
    },
  },
  watch: {
    reloadSvgMap(newVal, oldVal) {
      this.init();
    },
  },
  mounted() {
    this.init();
  },
  methods: {
    init() {
      this.loadingInstance = ElLoading.service({
        target: this.$refs.root,
        lock: true,
        text: '加载中...',
      });

      if (!this.svgPathOrReq) return;

      let promise;
      if (typeof this.svgPathOrReq === 'string') {
        promise = fetch(this.svgPathOrReq).then(response => response.text());
      } else {
        promise = this.svgPathOrReq();
      }

      // 销毁echarts实例
      this.destroyInstance = true;

      Promise.resolve(promise).then(svgStr => {
        // 旧echarts实例被销毁后处理新SVG图片
        this.$nextTick(() => {
          // 处理SVG的DOM，为其添加或移除某些元素或属性，同时获取信息
          const {svgEl, elInfo} = this.handleSvgDomAndGetInfo(svgStr);
          this.allElsInfo = elInfo.allElsInfo;

          // 执行处理SVG的DOM后的函数
          Promise.resolve(this.handleSvgDomAfter(elInfo)).then(() => {
            echarts.registerMap(this.mapName, {svg: svgEl});
            this.destroyInstance = false;
          });
        });
      });
    },
    handleSvgDomAndGetInfo(svgStr) {
      // 解析SVG字符串为DOM
      const svgDom = new DOMParser().parseFromString(svgStr, 'image/svg+xml');

      // 获取<svg>标签元素
      const svgEl = svgDom.querySelector('svg');

      // 获取<g>标签元素，并为其描宽度为1的黑边
      const gEl = svgEl.querySelector('g');
      gEl.setAttribute('stroke', 'black');
      gEl.setAttribute('stroke-width', '1');

      // 移除<title>标签元素
      const titleEl = svgEl.querySelector('g>title');
      if (titleEl) titleEl.remove();

      // 收集SVG中各元素圆与非圆元素，与其相应的名称，同时隐藏圆后续用散点图打点
      const elInfo = {
        allEls: [],
        allElsInfo: [],
        circleEls: [],
        noCircleEls: [],
      };
      svgEl.querySelectorAll(':not(image)').forEach(el => {
        if (!el.hasAttribute('id')) return;

        // 为标签元素设置name属性为id
        el.setAttribute('name', el.getAttribute('id'));

        // 收集allEls、allElsInfo、circleEls、noCircleEls信息
        elInfo.allEls.push(el);
        elInfo.allElsInfo.push(this.convertElToElInfo(el));
        if (el.nodeName === 'ellipse' || el.nodeName === 'circle') {
          elInfo.circleEls.push(el);

          // 隐藏圆后续用散点图打点
          el.remove();
        } else {
          elInfo.noCircleEls.push(el);
        }
      });

      return {svgEl: svgEl, elInfo: elInfo};
    },
    convertElToElInfo(el) {
      let circleInfo = {
        circle: false,
      };

      if (el.nodeName === 'ellipse' || el.nodeName === 'circle') {
        circleInfo = {
          circle: true,
          value: [el.getAttribute('cx'), el.getAttribute('cy')],
          itemStyle: {
            color: '#34C759FF'
          }
        };
      }

      return {
        id: el.getAttribute('id'),
        name: el.getAttribute('name'),
        node: el.nodeName,
        ...circleInfo,
      };
    },
    instancedAfter(echartsInstance) {
      echartsInstance.getZr().on('click', (event) => {
        if (!event.target) {
          this.$emit('clickBlank');
        }
      });

      echartsInstance.on('mouseover', 'series.map', (params) => {
        this.$emit('mouseoverBlock', params);
      });

      echartsInstance.on('click', 'series.map', (params) => {
        this.$emit('clickBlock', params);
      });

      echartsInstance.on('mouseover', 'series.effectScatter', (params) => {
        this.$emit('mouseoverCircle', params);
      });

      echartsInstance.on('click', 'series.effectScatter', (params) => {
        this.$emit('clickCircle', params);
      });

      echartsInstance.on('georoam', 'series.map', (params) => {
        if (!params.zoom) return;

        this.$emit('zoomMap', params.zoom);
      });

      this.$emit('instanced', echartsInstance);
      this.loadingInstance.close();
    },
  },
});
</script>